package meshGrammar;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;




import processing.core.PGraphics;



public class Mesh   {
	ArrayList<MeshEdge>edges=new ArrayList<MeshEdge>();
	ArrayList<MeshNode>nodes=new ArrayList<MeshNode>();
	ArrayList<MeshFace>faces=new ArrayList<MeshFace>();
	public Mesh(){
	
	}
	
	public Mesh(String file){
		this.loadMesh(file);
	}
	
	public Mesh getDual(){
		this.construct();
		Mesh mesh=new Mesh();
		for (int i=0;i<faces.size();i++){
			MeshFace face=faces.get(i);
			Vec3D c=face.getGravityCenter();
			mesh.addNode(c.x,c.y,c.z);
		}
		for (int i=0;i<edges.size();i++){
			MeshEdge edge=edges.get(i);
			MeshFace face1=edge.face1;
			MeshFace face2=edge.face2;
			MeshEdge newEdge=new MeshEdge((MeshNode)mesh.nodes.get(face1.id),(MeshNode)mesh.nodes.get(face2.id));
			mesh.edges.add(newEdge);
			
		}
		return mesh;
	}
	
	public void loadVertices(String file) {
		DataInputStream in = null;
		BufferedReader br = null;
		String strLine=null;
		try {
			FileInputStream fstream = new FileInputStream(file);
			in = new DataInputStream(fstream);
			br = new BufferedReader(new InputStreamReader(in));
			
			try {
				strLine = br.readLine();
				while (strLine != null) {
					int comment=0;
					if (strLine.startsWith("v ")) {
						strLine=strLine.replace("\\","");
						strLine=strLine.replace("  "," ");
						MeshNode node=new MeshNode();
						nodes.add(node);
						String[] data = strLine.split(" ");
						node.x= Float.valueOf(data[1]);
						node.y= Float.valueOf(data[2]);
						node.z= Float.valueOf(data[3]);
						if (data.length>5){
							if ("#".equals(data[4])){
								comment=Integer.valueOf(data[5]);
								node.comment=(short)comment;
							}
						}
					}
					strLine = br.readLine();
				}
			} catch (IOException e) {
				System.out.println(strLine);
				e.printStackTrace();
			}
		} catch (Exception e) {// Catch exception if any
			System.out.println(strLine);
			System.err.println("Error Node: " + e.getMessage());
		}
	}

	public void loadFaces(String file, int startIndexNode) {
		DataInputStream in = null;
		BufferedReader br = null;
		try {
			FileInputStream fstream = new FileInputStream(file);
			in = new DataInputStream(fstream);
			br = new BufferedReader(new InputStreamReader(in));
			String strLine;
			try {
				strLine = br.readLine();
				while (strLine != null) {
					if (strLine.startsWith("f")) {
						// if (!strLine.contains(" 0")){
						MeshFace face = new MeshFace();
						faces.add(face);
						int comment = 0;
						if (strLine.contains("#")) {
							String[] segments = strLine.split("#");
							strLine = segments[0];
							String partComment = segments[1];
							String[] cComments = partComment.split(" ");
							if (cComments.length > 1) {
								comment = Integer.valueOf(cComments[1]);
							}
						}
						String[] data = strLine.split(" ");
						for (int i = 1; i < data.length; i++) {
							String[] number = data[i].split("/");
							int nodeId = Integer.valueOf(number[0])
									+ startIndexNode;
							face.addNode(nodes.get(nodeId - 1));
						}
						face.comment = (short) comment;
					}
					strLine = br.readLine();
				}
				
			} catch (IOException e) {
				e.printStackTrace();
			}
		} catch (Exception e) {// Catch exception if any
			System.out.println("faces.size: "+faces.size());
			System.err.println("Error Face: " + e.getMessage());
		}
	}
	
	public void loadMesh(String file){
		nodes=new ArrayList<MeshNode>();
		loadVertices(file);
		faces=new ArrayList<MeshFace>();
		System.out.println("vertices loaded: "+nodes.size());
		loadFaces(file,0);
		System.out.println("faces loaded: "+faces.size());
	}
	public void loadAddMesh(String file){
		int startId=nodes.size();
		loadVertices(file);
		loadFaces(file,startId);
	}
	
	public void saveMesh(String file, boolean comments){
		FileWriter fw;
		try {
			fw = new FileWriter(file);
			BufferedWriter bw = new BufferedWriter(fw);
			if (comments){
			for (int i = 0; i <this.getNNodes(); i++) {
				MeshNode n = getNode(i);
				n.setId(i);
				bw.write("v "+n.getX()+" "+ n.getY()+" "+ n.getZ()+" # " + n.getComment());
				bw.newLine();
			}
			}
			else{
				for (int i = 0; i <this.getNNodes(); i++) {
					MeshNode n = getNode(i);
					n.setId(i);
					bw.write("v "+n.getX()+" "+ n.getY()+" "+ n.getZ());
					bw.newLine();
				}
			}
			for (int i = 0; i <getNFaces(); i++) {
				MeshFace f = getFace(i);
				String faceString="f";
				int[] ids = new int[f.getNNodes()];
				for (int j = 0; j < f.getNNodes(); j++) {
					MeshNode n = f.getNode(j);
					faceString+=" "+(n.getId()+1);
				}
				bw.write(faceString);
				bw.newLine();
			}
			bw.flush();
			bw.close();
			System.out.println("OBJ exported: "+file);
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
	}
	public void saveMesh(String file){
		
		saveMesh(file,false);
		
}
	public MeshFace addTri(int i1,int i2,int i3) {
	MeshFace face = new MeshFace(nodes.get(i1),nodes.get(i2),nodes.get(i3));
	this.addFace(face);
	return face;
}
	
	public MeshFace addFace(MeshNode[] nodes) {
		MeshFace face = new MeshFace(nodes);
		this.addFace(face);
		return face;
	}
	
	public ArrayList<MeshEdge> collectEdges(){
		ArrayList<MeshEdge> edges=new ArrayList<MeshEdge>();
		for (int i=0;i<nodes.size();i++){
			MeshNode n=getNode(i);
			for (int j=0;j<n.getEdges().size();j++){
				MeshEdge e=n.getEdge(j);
				if (e.getNode1()==n){
					e.setId(edges.size());
					edges.add(e);
				}
			}
		}
		return edges;
	}
	public void readFile(String file) {
		ArrayList<MeshNode>nodes=new ArrayList<MeshNode>();
		ArrayList<MeshEdge>edges=new ArrayList<MeshEdge>();
		try {
			BufferedReader in = new BufferedReader(new FileReader(file));
			String str;
			while ((str = in.readLine()) != null) {
				String[] code=str.split(" ");
				if (code[0].equals("v")){
					float x=Float.valueOf(code[0]);
					float y=Float.valueOf(code[1]);
					float z=Float.valueOf(code[2]);
					MeshNode n=new MeshNode(x,y,z);
				}
			}
			in.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	public void createEdgesCheckManifold(){
		edges=new ArrayList<MeshEdge>();
		
		for (int i=0;i<nodes.size();i++){
			MeshNode n=(MeshNode) nodes.get(i);
			n.getEdges().clear();
		}
		for (int i=0;i<faces.size();i++){
			MeshFace face=getFace(i);
			for (int j=0;j<face.getNNodes();j++){
				MeshNode node1= face.getNode(j);
				MeshNode node2= face.getNode(j+1);
				MeshEdge e=node1.getEdge(node2);
				if (e==null){
					e=new MeshEdge(node1,node2);
					e.setId(1);
					e.attach();
					this.addEdge(e);
				}
				else{
					e.setId(e.getId()+1);
				}
				e.setFace(node1, face);
			}
		}
		int iEdges=0;
		for (int i=0;i<edges.size();i++){
			MeshEdge edge=edges.get(i);
			if (edge.getId()>2){
				iEdges++;
				
			}
		}
		System.out.println("t edge: "+iEdges);
	}
	public void createEdges(){
		edges=new ArrayList<MeshEdge>();
		
		for (int i=0;i<nodes.size();i++){
			MeshNode n=(MeshNode) nodes.get(i);
			n.getEdges().clear();
		}
		for (int i=0;i<faces.size();i++){
			MeshFace face=getFace(i);
			for (int j=0;j<face.getNNodes();j++){
				MeshNode node1= face.getNode(j);
				MeshNode node2= face.getNode(j+1);
				MeshEdge e=node1.getEdge(node2);
				if (e==null){
					
					e=new MeshEdge(node1,node2);
					//e.setId(1);
					e.attach();
					this.addEdge(e);
				}
				/*else{
					e.setId(e.getId()+1);
				}*/
				e.setFace(node1, face);
			}
		}
		/*int iEdges=0;
		for (int i=0;i<edges.size();i++){
			MeshEdge edge=edges.get(i);
			if (edge.getId()>2){
				iEdges++;
				
			}
		}
		System.out.println("t edge: "+iEdges);*/
	}

	public int getNEdges(){
		return edges.size();
	}
	
	public void addEdge(MeshEdge edge){
		//edge.setId(this.getNEdges());
		this.edges.add(edge);
	}
	
	public void addNode(MeshNode node){
		node.setId(this.getNNodes());
		this.nodes.add(node);
		
	}
	public MeshNode addNode(float x,float y,float z){
		MeshNode node=new MeshNode(x,y,z);
		addNode(node);
		return node;
	}
	public int getNFaces() {
		return faces.size();
	}

	public int getNNodes() {
		return nodes.size();
	}
	
	
	public void removeUnusedNodes(){
		for (int i=0;i<nodes.size();i++){
			MeshNode p=nodes.get(i);
			p.setId(-1);
		}
		//System.out.println("before nodes:"+nodes.size());
		ArrayList<MeshNode> newNodes=new ArrayList<MeshNode>();
		for (int i=0;i<faces.size();i++){
			MeshFace t=faces.get(i);
			for (int j=0;j<t.getNNodes();j++){
				MeshNode n=(MeshNode) t.getNode(j);
				if (n.getId()==-1){
					n.setId(newNodes.size());
					newNodes.add(n);
				}
			}
		}
		nodes=newNodes;
		//System.out.println("after nodes:"+nodes.size());
	}
	
	public void addNodes(List<? extends MeshNode> nodes){
		this.nodes.addAll(nodes);
	}
	public void addFace(MeshFace face){
		face.setId(this.getNFaces());
		this.faces.add(face);
	}
	
	public void addQuad(int i1,int i2,int i3,int i4 ){
		MeshFace face=new MeshFace();
		face.addNode(this.getNode(i1));
		face.addNode(this.getNode(i2));
		face.addNode(this.getNode(i3));
		face.addNode(this.getNode(i4));
		this.faces.add(face);
	}
	public void addQuad(MeshNode n1,MeshNode n2,MeshNode n3,MeshNode n4 ){
		MeshFace face=new MeshFace();
		face.addNode(n1);
		face.addNode(n2);
		face.addNode(n3);
		face.addNode(n4);
		this.faces.add(face);
	}
	public void addTri(MeshNode n1,MeshNode n2,MeshNode n3 ){
		MeshFace face=new MeshFace();
		face.addNode(n1);
		face.addNode(n2);
		face.addNode(n3);
		this.faces.add(face);
	}
	
	public MeshFace getFace(int i) {
		// TODO Auto-generated method stub
		return faces.get(i);
	}

	
	public MeshNode getNode(int i) {
		// TODO Auto-generated method stub
		return nodes.get(i);
	}
	public MeshEdge getEdge(int i){
		return edges.get(i);
	}

	public  static Mesh constructMesh(ArrayList<? extends MeshFace>faces){
		Mesh mesh=new Mesh();
		for (int i=0;i<faces.size();i++){
			MeshFace face=(MeshFace)faces.get(i);
			for (int j=0;j<face.getNNodes();j++){
				MeshNode node=face.getNode(j);
				node.setId(-1);
			}
		}
		for (int i=0;i<faces.size();i++){
			MeshFace face=(MeshFace)faces.get(i);
			
			for (int j=0;j<face.getNNodes();j++){
				MeshNode node=face.getNode(j);
				if (node.getId()<0){
					node.setId(mesh.nodes.size());
					mesh.nodes.add(node);
				}
				
			}
		}
		mesh.faces=(ArrayList<MeshFace>) faces;
		return mesh;
	}
	public void construct(ArrayList<MeshFace>faces){
		nodes=new ArrayList<MeshNode>();
		this.faces=faces;
		for (int i=0;i<faces.size();i++){
			MeshFace face=(MeshFace)faces.get(i);
			for (int j=0;j<face.getNNodes();j++){
				MeshNode node=face.getNode(j);
				node.setId(-1);
			}
		}
		for (int i=0;i<faces.size();i++){
			MeshFace face=(MeshFace)faces.get(i);
			for (int j=0;j<face.getNNodes();j++){
				MeshNode node=face.getNode(j);
				if (node.getId()<0){
					node.setId(nodes.size());
					nodes.add(node);
				}
			}
		}
		construct();
	}
	
	public  Mesh constructMesh(){
		return new Mesh();
	}
	

	public  MeshNode constructNode(float x, float y, float z) {
		// TODO Auto-generated method stub
		return new MeshNode(x, y, z);
	}

	public static boolean contains(float x,float y,float z,List<? extends MeshFace>faces){
		boolean nI=true;
		Vec3D start=new Vec3D(x,y,z);
		Vec3D end=new Vec3D(x,y,z+1);
		for (int i=0;i<faces.size();i++){
			MeshFace face=faces.get(i);
			if (Vec3D.rayTriIntersection(face.getNode(0), face.getNode(1), face.getNode(2), start, end)!=null){
				nI=!nI;
			}
		}
		return nI;
	}
	
	
	public float getVolumeOfSolid(){
		float vol=0;
		for (int i=0;i<this.getNFaces();i++){
			MeshFace face=this.getFace(i);
			vol+=face.signedVolumeOfTriangle();
		}
		return vol;
	}
	
	public  MeshFace constructFace() {
		// TODO Auto-generated method stub
		return new MeshFace();
	}
	public void translate(float x,float y,float z){
		for (int i=0;i<nodes.size();i++){
			MeshNode node=nodes.get(i);
			node.x+=x;
			node.y+=y;
			node.z+=z;
		}
	}
	public void scale(float x,float y,float z){
		for (int i=0;i<nodes.size();i++){
			MeshNode node=nodes.get(i);
			node.x*=x;
			node.y*=y;
			node.z*=z;
		}
	}
	public void normalise(){
		normalise(1);
	}
	public Box3D getBounds() {
		Box3D bounds = new Box3D();
		bounds.setEmpty();
		for (int i=0;i<nodes.size();i++){
			MeshNode node=nodes.get(i);
			bounds.addPoint(node.x,node.y,node.z);
		}
		
		return bounds;
	}
	public void normalise(float dim){
		Box3D b3d=this.getBounds();
		float maxD = b3d.getMaxDimension();
		float scaleF =dim / maxD;
		translate(-b3d.getCenterX(), -b3d.getCenterY(),
				-b3d.getCenterZ());
		scale(scaleF, scaleF, scaleF);
	}

	public void normaliseToDim(float dim){
		Box3D b3d=this.getBounds();
		float maxD = b3d.getMaxDimension();
		float scaleF =dim / maxD;
		scale(scaleF, scaleF, scaleF);
	}
	
	public void construct(){
		rebuildIds();
		createEdges();
		rebuildIds();
	}
	
	
	public void rebuildIds() {
		for (int i = 0; i < getNNodes(); i++) {
			getNode(i).setId(i);
		}
		for (int i = 0; i < getNFaces(); i++) {
			getFace(i).setId(i);
		}
		for (int i = 0; i < getNEdges(); i++) {
			getEdge(i).setId(i);
		}
	}
	
	public void clear(){
		for (int i=0;i<faces.size();i++){
			MeshFace face=(MeshFace) faces.get(i);
			face.nodes=null;
		}
		for (int i=0;i<edges.size();i++){
			MeshEdge edge=edges.get(i);
			edge.face1=null;
			edge.face2=null;
			edge.n1=null;
			edge.n2=null;
		}
		faces.clear();
		edges.clear();
		nodes.clear();
	}
	public Mesh getCopyNodes() {
		Mesh newMesh = new Mesh();
		for (int i = 0; i < getNNodes(); i++) {
			MeshNode n = getNode(i);
			MeshNode newNode = new MeshNode(n.getCopy());
			newNode.comment=n.comment;
			newMesh.addNode(newNode);
		}
		newMesh.construct();
		return newMesh;
	}

	public Mesh getCopy() {
		rebuildIds();
		Mesh newMesh = getCopyNodes();
		for (int i = 0; i < getNFaces(); i++) {
			MeshFace f = getFace(i);
			MeshFace newFace=new MeshFace();
			
			newFace.setData(f.getData());
			newFace.comment=f.comment;
			for (int j = 0; j < f.getNNodes(); j++) {
				MeshNode n = f.getNode(j);
				newFace.addNode(newMesh.getNode(n.getId()));
			}
			newMesh.addFace(newFace);
		}
		newMesh.construct();
		return newMesh;
	}
	
	public void invertFaces(){
		for (int i=0;i<this.getNFaces();i++){
			MeshFace face=this.getFace(i);
			face.turn();
		}
	}
	public void XToZ(){
		for (int i=0;i<this.getNNodes();i++){
			MeshNode node=this.getNode(i);
			XtoZ(node);
		}
	}
	public void YToZ(){
		for (int i=0;i<this.getNNodes();i++){
			MeshNode node=this.getNode(i);
			YtoZ(node);
		}
	}
	public void XtoZ(MeshNode node){
		float temp=node.getX();
		node.setX(node.getZ());
		node.setZ(temp);
	}
	public void YtoZ(MeshNode node){
		float temp=node.getY();
		node.setY(node.getZ());
		node.setZ(temp);
	}
	
	
	
	
	
	public  void triangulate(){
		ArrayList<MeshFace>newFaces=new ArrayList<MeshFace>();
		for (int i = 0; i < faces.size(); i++) {
			MeshFace f = getFace(i);
			if (f.getNNodes()==3){
				newFaces.add(f);
			}
			else if (f.getNNodes()==4){
				
				ArrayList<MeshNode> newFNodes = new ArrayList<MeshNode>();
				MeshFace f1=new MeshFace();
				f1.setData(f.getData());
				for (int j = 0; j < 3; j++) {
					MeshNode n = f.getNode(j);
					f1.addNode(n);
				}
				newFaces.add(f1);
				MeshFace f2=new MeshFace();
				f2.setData(f.getData());
				for (int j = 2; j < 5; j++) {
					MeshNode n = f.getNode(j);
					f2.addNode(n);
				}
				newFaces.add(f2);
			}
			else{
				System.out.println("nodes: "+f.getNNodes());
			}
		}
		this.faces=newFaces;
		construct();
		
	}

	

	public ArrayList<MeshEdge> getEdges() {
		return edges;
	}
}
